#include <glib.h>
#include <gio/gio.h>
#include <string.h>
#include <stdlib.h>

#define EXAMPLE_SERVICE_NAME "CYG.API"
#define EXAMPLE_INTERFACE_NAME "Simples.Example"

// XML interface definition
static const gchar *introspection_xml =
    "<node>"
    "  <interface name='Simples.Example'>"
    "    <method name='SayHello'>"
    "      <arg type='s' direction='in' name='name'/>"
    "      <arg type='s' direction='out' name='response'/>"
    "    </method>"
    "    <method name='AddNumbers'>"
    "      <arg type='i' direction='in' name='a'/>"
    "      <arg type='i' direction='in' name='b'/>"
    "      <arg type='i' direction='out' name='sum'/>"
    "    </method>"
    "    <signal name='ServiceSignal'>"
    "      <arg type='s' name='message'/>"
    "    </signal>"
    "  </interface>"
    "</node>";

// Structure to hold node information
typedef struct {
    gchar *object_path;
    guint registration_id;
} NodeInfo;

static GPtrArray *node_list = NULL;  // List to store all registered nodes
static GDBusNodeInfo *introspection_data = NULL;

static void handle_method_call(GDBusConnection *connection,
                               const gchar *sender,
                               const gchar *object_path,
                               const gchar *interface_name,
                               const gchar *method_name,
                               GVariant *parameters,
                               GDBusMethodInvocation *invocation,
                               gpointer user_data);

static gboolean emit_signal(gpointer user_data);

static const GDBusInterfaceVTable interface_vtable = {
    handle_method_call,     // method call
    NULL,                   // property setter
    NULL                    // property getter
};

// Create a new object path based on index
static gchar* create_object_path(gint index) {
    return g_strdup_printf("/Simples/Example%d", index);
}

int main()
{
    GError *error = NULL;
    GDBusConnection *connection;
    GMainLoop *loop;
    guint owner_id;
    
    // Initialize node list
    node_list = g_ptr_array_new_with_free_func(g_free);
    
    loop = g_main_loop_new(NULL, FALSE);
    
    // Connect to D-Bus
    connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
    if (connection == NULL) {
        g_printerr("Failed to connect to D-Bus: %s\n", error->message);
        g_error_free(error);
        return -1;
    }
    
    // Parse the interface from XML
    introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, &error);
    if (introspection_data == NULL) {
        g_printerr("Failed to parse introspection XML: %s\n", error->message);
        g_error_free(error);
        g_object_unref(connection);
        return -1;
    }
    
    // Register multiple objects with the same interface
    gint num_nodes = 3;  // Number of nodes to create
    for (gint i = 0; i < num_nodes; i++) {
        gchar *object_path = create_object_path(i);
        guint registration_id;
        
        // Register object
        registration_id = g_dbus_connection_register_object(
            connection,
            object_path,
            introspection_data->interfaces[0],  // Use parsed interface
            &interface_vtable,
            GINT_TO_POINTER(i),  // Pass node index as user_data
            NULL,               // user_data_free_func
            &error);
        
        if (error != NULL) {
            g_printerr("Failed to register object %s: %s\n", object_path, error->message);
            g_error_free(error);
            error = NULL;
            g_free(object_path);
            continue;
        }
        
        // Store node information
        NodeInfo *node_info = g_malloc(sizeof(NodeInfo));
        node_info->object_path = object_path;
        node_info->registration_id = registration_id;
        g_ptr_array_add(node_list, node_info);
        
        g_print("Registered node %d at path: %s\n", i, object_path);
    }
    
    // Own the connection name
    owner_id = g_bus_own_name_on_connection(
        connection,
        EXAMPLE_SERVICE_NAME,
        G_BUS_NAME_OWNER_FLAGS_NONE,
        NULL,   // on_name_acquired
        NULL,   // on_name_lost
        NULL,   // user_data
        NULL);  // user_data_free_func
    
    g_print("D-Bus service '%s' is running with %d nodes\n", 
            EXAMPLE_SERVICE_NAME, node_list->len);
    
    for (guint i = 0; i < node_list->len; i++) {
        NodeInfo *info = (NodeInfo*)g_ptr_array_index(node_list, i);
        g_print("  Node %d: %s\n", i, info->object_path);
    }
    
    g_print("Press Ctrl+C to exit.\n");
    
    // Emit signals periodically
    g_timeout_add_seconds(5, (GSourceFunc)emit_signal, connection);
    
    // Run main loop
    g_main_loop_run(loop);
    
    // Cleanup
    g_bus_unown_name(owner_id);
    
    // Unregister all objects
    for (guint i = 0; i < node_list->len; i++) {
        NodeInfo *info = (NodeInfo*)g_ptr_array_index(node_list, i);
        g_dbus_connection_unregister_object(connection, info->registration_id);
    }
    
    g_ptr_array_free(node_list, TRUE);
    g_dbus_node_info_unref(introspection_data);
    g_object_unref(connection);
    g_main_loop_unref(loop);
    
    return 0;
}

static void handle_method_call(GDBusConnection *connection,
                               const gchar *sender,
                               const gchar *object_path,
                               const gchar *interface_name,
                               const gchar *method_name,
                               GVariant *parameters,
                               GDBusMethodInvocation *invocation,
                               gpointer user_data)
{
    gint node_index = GPOINTER_TO_INT(user_data);
    g_print("Received method call: %s from %s on node %d (%s)\n", 
            method_name, sender, node_index, object_path);
    
    if (g_strcmp0(method_name, "SayHello") == 0) {
        const gchar *name;
        g_variant_get(parameters, "(&s)", &name);
        
        gchar *response = g_strdup_printf("Hello, %s! (from node %d)", name, node_index);
        g_dbus_method_invocation_return_value(invocation, 
                                             g_variant_new("(s)", response));
        g_free(response);
        
        g_print("Responded to SayHello method on node %d\n", node_index);
    } 
    else if (g_strcmp0(method_name, "AddNumbers") == 0) {
        gint32 a, b;
        g_variant_get(parameters, "(ii)", &a, &b);
        
        gint32 sum = a + b;
        g_dbus_method_invocation_return_value(invocation, 
                                             g_variant_new("(i)", sum));
        
        g_print("Responded to AddNumbers method on node %d: %d + %d = %d\n", 
                node_index, a, b, sum);
    } 
    else {
        GError *error = g_error_new(G_DBUS_ERROR, 
                                   G_DBUS_ERROR_UNKNOWN_METHOD,
                                   "Unknown method: %s", method_name);
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
        
        g_print("Rejected unknown method: %s on node %d\n", method_name, node_index);
    }
}

// Emit signal on all registered nodes
static gboolean emit_signal(gpointer user_data)
{
    static guint counter = 0;
    GDBusConnection *connection = (GDBusConnection *)user_data;
    GError *error = NULL;
    
    counter++;
    
    // Emit signal on each registered node
    for (guint i = 0; i < node_list->len; i++) {
        NodeInfo *info = (NodeInfo*)g_ptr_array_index(node_list, i);
        
        GVariant *signal_value = g_variant_new("(s)", 
            g_strdup_printf("Service is alive from node %d! Count: %d", i, counter));
        
        g_dbus_connection_emit_signal(connection,
                                     NULL,
                                     info->object_path,
                                     EXAMPLE_INTERFACE_NAME,
                                     "ServiceSignal",
                                     signal_value,
                                     &error);
        
        if (error != NULL) {
            g_printerr("Error emitting signal on node %d: %s\n", i, error->message);
            g_error_free(error);
            error = NULL;
        } else {
            g_print("Emitted signal on node %d: Count: %d\n", i, counter);
        }
    }
    
    return G_SOURCE_CONTINUE;
}